package com.boil.mybatis;

import com.boil.exception.BaseException;
import com.boil.util.Constants;
import com.boil.util.Page;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/**
 * Created by ChenYong on 2017-06-20.
 * <br>
 * Mybatis 拦截器。
 * <br>
 * 自动处理分页，非常牛逼的拦截器。
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisInterceptor implements Interceptor {
    /**
     * 数据库方言
     */
    private static final String DB_DIALECT = "dbDialect";
    /**
     * 绑定 sql
     */
    private static final String BOUNDSQL_SQL = "delegate.boundSql.sql";
    /**
     * 映射语句
     */
    private static final String MAPPED_STATEMENT = "delegate.mappedStatement";
    /**
     * 参数机制
     */
    private static final String PARAMETER_HANDLER = "delegate.parameterHandler";
    /**
     * 日志
     */
    private static Logger log = LoggerFactory.getLogger(MybatisInterceptor.class);
    /**
     * 数据库方言
     */
    private String dbDialect;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 语句处理实例
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 元对象
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        // 映射语句实例
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(MAPPED_STATEMENT);
        // 查询条件
        Object statementHandlerParam = statementHandler.getParameterHandler().getParameterObject();

        // 如果是分页查询
        if ((statementHandlerParam != null) && (statementHandlerParam instanceof Page)) {
            // 原始 sql 语句
            final String oldSql = mappedStatement.getBoundSql(statementHandlerParam).getSql().trim();
            // 分页实例
            Page page = (Page) statementHandlerParam;
            // 数据库连接对象
            Connection connection = (Connection) invocation.getArgs()[Constants.VALUE_0];
            // 预处理语句对象
            PreparedStatement preparedStatement = null;
            // 结果集
            ResultSet resultSet = null;

            try {
                // 获取预处理语句对象
                preparedStatement = connection.prepareStatement(getCountSql(oldSql));

                // 获取参数机制实例
                ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue(PARAMETER_HANDLER);
                parameterHandler.setParameters(preparedStatement);

                // 获取结果集
                resultSet = preparedStatement.executeQuery();

                if (resultSet.next()) {
                    page.setCount(resultSet.getInt(Constants.VALUE_1));
                }

                // 分页 sql 语句
                String pagingSql = Constants.EMPTY_STR;

                // MySQL 数据库
                if (Constants.MYSQL_DIALECT.equalsIgnoreCase(dbDialect)) {
                    pagingSql = getMySQLPagingSql(page, oldSql);

                    // Oracle 数据库
                } else if (Constants.ORALCE_DIALECT.equalsIgnoreCase(dbDialect)) {
                    pagingSql = getOraclePagingSql(page, oldSql);

                    // 其他数据库
                } else {
                    throw new BaseException("目前分页操作只支持 MySQL 和 Oracle 数据库！");
                }

                // 替换旧 sql 语句
                metaObject.setValue(BOUNDSQL_SQL, pagingSql);
            } catch (Exception e) {
                throw e;

                // 释放资源
            } finally {
                if (resultSet != null) {
                    resultSet.close();
                }

                if (preparedStatement != null) {
                    preparedStatement.close();
                }
            }
        }

        log.info("SQL Args--->{}", statementHandlerParam);
        log.info("SQL--->{}", statementHandler.getBoundSql().getSql());

        // 放行，让它走！！！
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    /**
     * 读取 MyBatis-config.xml 配置文件中的信息。
     */
    @Override
    public void setProperties(Properties prop) {
        this.dbDialect = prop.getProperty(DB_DIALECT, Constants.MYSQL_DIALECT);
    }

    /**
     * 获取统计总记录数的 sql 语句。
     *
     * @param oldSql 原始 sql 语句
     * @return 统计总记录数的 sql 语句
     */
    private String getCountSql(String oldSql) {
        // 将 sql 语句转换为大写
        oldSql = oldSql.toUpperCase().replaceAll("ORDER(\\s)+BY", "ORDER BY");

        if (oldSql.contains("ORDER BY")) {
            return String.format("SELECT COUNT(1) FROM (%s) T_COUNT", oldSql.substring(Constants.VALUE_0, (oldSql.lastIndexOf("ORDER BY") - Constants.VALUE_1)));
        } else {
            return String.format("SELECT COUNT(1) FROM (%s) T_COUNT", oldSql);
        }
    }

    /**
     * 获取 MySQL 分页 sql 语句。
     *
     * @param page   分页实例
     * @param oldSql 原始 sql 语句
     * @return MySQL 分页 sql 语句
     */
    private String getMySQLPagingSql(Page<?> page, String oldSql) {
        // 将 sql 语句转换为大写
        oldSql = oldSql.toUpperCase().replaceAll("ORDER(\\s)+BY", "ORDER BY");

        // 重构排序语句
        if (oldSql.contains("ORDER BY")) {
            oldSql = String.format("SELECT * FROM (%s) %s", //
                    oldSql.substring(Constants.VALUE_0, (oldSql.lastIndexOf("ORDER BY") - Constants.VALUE_1)), //
                    oldSql.substring(oldSql.lastIndexOf("ORDER BY")));
        }

        // 对原始 Sql 语句追加 MySQL 分页
        StringBuilder sb = new StringBuilder(oldSql);
        sb.append(" LIMIT ");
        sb.append(page.getStartRecords());
        sb.append(", ");
        sb.append(page.getPageSize());

        return sb.toString();
    }

    /**
     * 获取 Oracle 分页 sql 语句。
     *
     * @param page   分页实例
     * @param oldSql 原始 sql 语句
     * @return Oracle 分页 sql 语句
     */
    private String getOraclePagingSql(Page<?> page, String oldSql) {
        // 将 sql 语句转换为大写
        oldSql = oldSql.toUpperCase().replaceAll("ORDER(\\s)+BY", "ORDER BY");

        // 重构排序语句
        if (oldSql.contains("ORDER BY")) {
            oldSql = String.format("SELECT * FROM (%s) %s", //
                    oldSql.substring(Constants.VALUE_0, (oldSql.lastIndexOf("ORDER BY") - Constants.VALUE_1)), //
                    oldSql.substring(oldSql.lastIndexOf("ORDER BY")));
        }

        // 对原始 Sql 语句追加 Oracle 分页
        StringBuilder sb = new StringBuilder();
        sb.append("SELECT TT2.* FROM ");
        sb.append("(");
        sb.append("SELECT TT1.*, ROWNUM RN ");
        sb.append("FROM (");
        sb.append(oldSql);
        sb.append(") TT1 ");
        sb.append("WHERE ROWNUM <= " + page.getEndRecords());
        sb.append(") TT2 ");
        sb.append("WHERE TT2.RN > " + page.getStartRecords());

        return sb.toString();
    }
}